<script type="text/jsx">
import classNames from 'classnames'
import ZMask from './ZMask'

export default {
  name: 'ZDialog',
  props: {
    visible: {
      type: Boolean,
      default: true
    },
    customClass: {
      type: String,
      default: ''
    },
    title: {
      type: [String, Object],
      default: ''
    },
    body: {
      type: [String, Object],
      default: ''
    },
    // false时隐藏footer
    footer: {
      type: [String, Object, Boolean],
      default: null
    },
    // 正极按钮文字，为''时不渲染
    positiveBtnText: {
      type: String,
      default: '确认'
    },
    // 负极按钮文字，为''时不渲染
    negativeBtnText: {
      type: String,
      default: '关闭'
    },
    // 是否只显示取消按钮
    onlyCancel: {
      type: Boolean,
      default: false
    },
    // 按钮点击后的回调
    beforeClose: {
      type: Function,
      default: null
    },
    // 是否显示body的padding
    showBodyPadding: {
      type: Boolean,
      default: true
    }
  },
  data () {
    return {
      index: 0,
      fadeInEnabled: false,
      fadeOutEnabled: false,
      animationNoneEnabled: false,
      scrollTop: ''
    }
  },
  computed: {
    dialogDom () {
      return this.$el?.querySelector('.v-dialog')
    },
    positiveButtonText () {
      return this.onlyCancel ? '关闭' : this.positiveBtnText
    },
    negativeButtonText () {
      return this.onlyCancel ? '' : this.negativeBtnText
    }
  },
  watch: {
    visible (newVal, oldVal) {
      if (newVal && !oldVal) {
        this.appendMeToBody()
      }
    },
    dialogIndex (newVal) {
      if (newVal < this.index) {
        this.close(true)
      }
    }
  },
  mounted () {
    // 阻止底部页面滚动
    this.afterOpenScroll()

    // 当visible为false时，为以在template嵌入组件的方式调用。
    // 要延迟dom插入body的行为到visible变为true时的阶段，以保证层级的正确性。
    if (this.visible) {
      this.appendMeToBody()
    }

    // 处理在body组件里面的按钮发送的close事件
    if (typeof this.body === 'object') {
      this.body?.componentInstance?.$on('close', () => this.close())
    }
    if (typeof this.footer === 'object') {
      this.footer?.componentInstance?.$on('close', () => this.close())
    }
  },
  destroyed () {
    if (this.$el && this.$el.parentNode) {
      this.$el.parentNode.removeChild(this.$el)
    }
  },
  methods: {
    afterOpenScroll () {
      this.scrollTop = document.scrollingElement.scrollTop
      document.body.style.position = 'fixed'
      document.body.style.top = -this.scrollTop + 'px'
    },
    beforeCloseScroll () {
      document.body.style.position = ''
      document.body.style.top = ''
      document.scrollingElement.scrollTop = this.scrollTop
    },
    close (pure) {
      // 打开底部页面滚动
      this.beforeCloseScroll()

      this.fadeOutEnabled = true
      setTimeout(() => {
        // 在template中使用此组件时，可能会因为Vue的dom复用机制导致过渡class保留，所以需要手动重置来保证再次弹出时弹框的显示正确
        this.fadeInEnabled = false
        this.fadeOutEnabled = false
        this.animationNoneEnabled = false

        this.$el.parentNode?.removeChild(this.$el) // 因为可能会没有设置visible prop，所有要这一句才能确保在缺省时删除dialog
        this.$emit('update:visible', false)
      }, 150)
    },
    appendMeToBody () {
      document.body.appendChild(this.$el)
      window.requestAnimationFrame(() => {
        this.fadeInEnabled = true
        // 动画完成后清除transition，以解决拖动窗口时卡顿的问题
        setTimeout(
          () => { this.animationNoneEnabled = true }, 250
        )
      })

      // 设置本dialog索引（同步遮罩用）
      this.index = this.dialogIndex
    },
    handleAction (actionType) {
      if (!this.beforeClose) {
        this.close()
      } else {
        this.beforeClose(actionType, this.close.bind(this))
      }
    },
    handleMouseDown (e) {
      if (e.target !== e.currentTarget) {
        return
      }
      const [originalX, originalY] = [e.clientX, e.clientY]
      const style = this.dialogDom.style
      const [marginLeftOffset, marginTopOffset] = [parseInt(style.marginLeft || 0), parseInt(style.marginTop || 0)]

      // 拖动处理
      const mouseMoveListener = (e) => {
        const [offsetX, offsetY] = [e.clientX - originalX, e.clientY - originalY]
        window.requestAnimationFrame(() => {
          style.marginLeft = marginLeftOffset + offsetX * 2 + 'px'
          style.marginTop = marginTopOffset + offsetY * 2 + 'px'
        })
      }
      const mouseUpListener = () => {
        window.removeEventListener('mousemove', mouseMoveListener)
        window.removeEventListener('mouseup', mouseUpListener)
      }
      window.addEventListener('mousemove', mouseMoveListener)
      window.addEventListener('mouseup', mouseUpListener)
    }
  },
  render () {
    const dialogClass = classNames('v-dialog', this.customClass, {
      'v-dialog--fade-in': this.fadeInEnabled,
      'v-dialog--animation-none': this.animationNoneEnabled,
      'v-dialog--fade-out': this.fadeOutEnabled
    })

    return this.visible && (
      <div class="v-dialog__wrapper">
        {<ZMask zIndex={-1} />}
        <div class={dialogClass}>
          <div class="v-dialog__header" onMousedown={this.handleMouseDown}>
            <span class="v-dialog__header__title">
              {this.$slots.title ?? this.title}
            </span>
            <a-icon type="close" onClick={this.close} />
          </div>
          <div class={classNames('v-dialog__body', { 'v-dialog__body--spacing': this.showBodyPadding })}>
            {this.$slots.default ?? this.body}
          </div>
          {
            this.$slots.footer
              ? (<div class="v-dialog__footer">{this.$slots.footer}</div>)
              : (this.footer !== false && (
                <div class="v-dialog__footer">
                  {
                    this.footer || [
                      this.negativeButtonText && (
                        <a-button onClick={() => this.handleAction('cancel')}>{this.negativeButtonText}</a-button>),
                      this.positiveButtonText && (<a-button type="primary"
                        onClick={() => this.handleAction('confirm')}>{this.positiveButtonText}</a-button>)
                    ]
                  }
                </div>
              ))
          }
        </div>
      </div>
    )
  }
}
</script>

<style lang="less">
.v-dialog {
  background: #fff;
  border-radius: 2px;
  box-shadow: 1px 1px 50px rgba(0, 0, 0, 0.3);
  min-width: 300px;
  opacity: 0.3;
  transform: scale(0.5);
  transition: 0.25s;

  &--animation-none {
    transition: none;
  }

  &--fade-in {
    opacity: 1;
    transform: scale(1);
  }

  &--fade-out {
    opacity: 0;
    transform: scale(0.5);
    transition: 0.15s;
  }

  &__wrapper {
    align-items: center;
    display: flex;
    justify-content: center;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: 0;
    z-index: 1000;
  }

  &__header {
    display: flex;
    align-items: center;
    background: #fff;
    border-bottom: 1px solid #eee;
    color: rgba(0, 0, 0, 0.85);
    cursor: all-scroll;
    font-size: var(--font-size-16);
    height: 55px;
    justify-content: space-between;
    padding: 0 16px 0 20px;

    &__title {
      pointer-events: none;
    }

    &__close {
      cursor: pointer;
      user-select: none;
      font-size: 18px;
    }
  }

  &__body {
    font-size: 13px;
    min-height: 50px;
  }

  &__footer {
    padding: 10px 16px;
    text-align: right;
    border-top: 1px solid rgba(235, 235, 235, 1);
    .ant-btn {
      margin-right: 10px;
    }
  }
}
</style>
